﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using DataAcquisition.Commands;
using DataAcquisition.TransferObjects;

namespace DataAcquisition
{
    class SocketAdaptor
    {
        private readonly Socket socket;

        private readonly IDataReceiver dataReceiver;

        private BasicInfo basicInfo;

        private List<byte[]> data = new List<byte[]>();

        public bool IsActive { get; set; }

        public SocketAdaptor(IDataReceiver dataReceiver)
        {
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            this.dataReceiver = dataReceiver;
        }

        public void Connect(string host, int port)
        {
            socket.Connect(host, port);
        }

        public void SendCommand(short command, short subtype)
        {
            Message message = new Message { Type = Message.CtrlType, Code = command, Subcode = subtype };
            SendMessage(message);
        }      

        public void SendMessage(Message message)
        {
            byte[] data = message.GetServerMessage();
            socket.Send(data);
        }

        public void StartReceiving()
        {
            socket.Blocking = true;

            Thread receiveThread = new Thread(new ThreadStart(Receive));
            receiveThread.Start();

            SendCommand((short)ControlCode.ClientControlCode, (short)ClientCommand.RequestBasicInfo);          
        }

        public void StopReceiving()
        {
            SendCommand((short)ControlCode.ClientControlCode, (short) ClientCommand.RequestStopData);
            IsActive = false;
        }

        private void Receive()
        {
            while (IsActive)
            {
                Message message = ReceiveMessage();

                if (message != null)
                {
                    if (message.IsControlMessage)
                    {
                        ProcessControlMessage(message);
                    }
                    else if (message.IsDataMessage)
                    {
                        ProcessDataMessage(message);
                    }
                    else
                    {
                        throw new ArgumentException("Undefined command type");
                    }
                }               
            }
        }

        private Message ReceiveMessage()
        {
            Message message = null;

            byte[] header = new byte[Message.HeaderSize];
            SocketError headerErrorCode;
            int headerBytesReceived = socket.Receive(header, 0, Message.HeaderSize, SocketFlags.None, out headerErrorCode);

            if (headerBytesReceived > 0)
            {
                message = Message.FromServerMessage(header);

                if (message.DataSize > 0)
                {
                    byte[] data = new byte[message.DataSize];
                    SocketError dataErrorCode;
                    int dataBytesReceived = socket.Receive(data, 0, message.DataSize, SocketFlags.None, out dataErrorCode);
                    message.Data = data;
                }
            }           

            return message;
        }

        private void ProcessControlMessage(Message message)
        {
            switch (message.Code)
            {
                case (short)ControlCode.GeneralControlCode:
                    if (message.Subcode == (short)GeneralCommand.ClosingUp)
                    {
                        IsActive = false;
                        dataReceiver.Disconnected();
                    }
                    break;
                case (short)ControlCode.ServerControlCode:
                    switch (message.Subcode)
                    {
                        case (short)ServerCommand.StartAcquisition:
                            SendCommand((short)ControlCode.ClientControlCode, (short)ClientCommand.RequestStartData);
                            dataReceiver.StartAcquisition();
                            break;
                        case (short)ServerCommand.StopAcquisition:
                            dataReceiver.StopAcquisition();
                            IsActive = false;
                            break;
                        case (short)ServerCommand.StartImpedance:
                            dataReceiver.StartImpedance();
                            break;
                    }
                    break;
                default:
                    throw new ArgumentException("Unexpected received control command");
            }
        }

        private void ProcessDataMessage(Message message)
        {
            switch (message.Code)
            {
                case (short) DataCode.DataType_InfoBlock:
                    switch (message.Subcode)
                    {
                        case (short)InfoBlockType.InfoType_Version:
                        break;
                        case (short)InfoBlockType.InfoType_EdfHeader:
                        break;
                        case (short)InfoBlockType.InfoType_BasicInfo:
                            basicInfo = BasicInfo.FromServerMessage(message.Data);
                            dataReceiver.ProcessBasicInfo(basicInfo);
                        break;
                    }
                    break;
                case (short)DataCode.DataType_EegData:
                    data.Add(message.Data);
                    break;
                default:
                    throw new ArgumentException("Unexpected received data command");
            }
            
        }

        public bool HasData
        {
            get
            {
                return data.Count > 0;
            }
        }

        public EegData GetData()
        {   
            EegData eegData = new EegData 
            { 
                EegChannelsNumber = basicInfo.EegChannelsNumber,
                EventChannelsNumber = basicInfo.EventChannelsNumber,
                SamplingRate = basicInfo.SamplingRate
            };

            byte[] rawData = data[0];

            if (basicInfo.DataSize != 4 && basicInfo.DataSize != 2)
            {
                throw new ArgumentException("Wrong data size");
            }

            bool is32bit = basicInfo.DataSize == 4;

            int dataPointer = 0;
            for (int i = 0; i < basicInfo.SampelsPerBlock; i++)
            {
                float[] sample = new float[basicInfo.AllChannelsNumber];
                for (int j = 0; j < basicInfo.AllChannelsNumber; j++)
                {
                    int channelSample = is32bit ? BitConverter.ToInt32(rawData, dataPointer) : BitConverter.ToInt16(rawData, dataPointer);
                    sample[j] = channelSample * basicInfo.Resolution;
                    dataPointer += basicInfo.DataSize;
                }
                eegData.Add(sample);
            }

            data.RemoveAt(0);

            return eegData;
        }
        
    }
}
